001 /*
002 * Copyright 2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.station.server;
020
021 import java.io.IOException;
022 import java.rmi.ConnectException;
023 import java.rmi.RemoteException;
024 import java.util.Enumeration;
025 import java.util.ArrayList;
026 import java.util.Properties;
027 import java.util.EventObject;
028 import java.util.EventListener;
029
030 import net.dpml.station.info.StartupPolicy;
031 import net.dpml.station.info.ApplicationDescriptor;
032
033 import net.dpml.component.Component;
034 import net.dpml.component.Provider;
035
036 import net.dpml.station.Callback;
037 import net.dpml.station.ProcessState;
038 import net.dpml.station.Application;
039 import net.dpml.station.ApplicationException;
040 import net.dpml.station.ApplicationListener;
041 import net.dpml.station.ApplicationEvent;
042
043 import net.dpml.util.Logger;
044 import net.dpml.lang.PID;
045
046 /**
047 * The RemoteApplication is the default implementation of a remotely
048 * accessible Aplication.
049 *
050 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
051 * @version 1.0.0
052 */
053 public class RemoteApplication extends UnicastEventSource implements Callback, Application
054 {
055 private final Logger m_logger;
056 private final ApplicationDescriptor m_descriptor;
057 private final String m_id;
058 private final int m_port;
059
060 private ProcessState m_state = ProcessState.IDLE;
061 private PID m_pid = null;
062 private Component m_handler = null;
063 private Provider m_instance = null;
064 private Process m_process = null;
065 private Exception m_error = null;
066
067 /**
068 * Creation of an application instance.
069 *
070 * @param logger the assigned logging channel
071 * @param descriptor the application descriptor
072 * @param id the application key
073 * @param port the rmi registry port on which the station is registered
074 * @exception RemoteException if a remote exception occurs
075 */
076 public RemoteApplication(
077 Logger logger, ApplicationDescriptor descriptor, String id, int port )
078 throws RemoteException
079 {
080 super( logger );
081
082 m_logger = logger;
083 m_descriptor = descriptor;
084 m_id = id;
085 m_port = port;
086 }
087
088 //-------------------------------------------------------------------------------
089 // Callback
090 //-------------------------------------------------------------------------------
091
092 /**
093 * Method invoked by a process to signal that the process has
094 * commenced startup.
095 *
096 * @param pid the process identifier
097 * @param handler the component handler
098 * @exception ApplicationException if an application exception occurs
099 */
100 public void started( PID pid, Component handler ) throws ApplicationException
101 {
102 if( null != m_pid )
103 {
104 final String error =
105 "PID already assigned.";
106 throw new ApplicationException( error );
107 }
108 synchronized( m_state )
109 {
110 m_pid = pid;
111 m_handler = handler;
112 try
113 {
114 m_instance = handler.getProvider();
115 setProcessState( ProcessState.STARTED );
116 }
117 catch( Exception e )
118 {
119 try
120 {
121 handler.decommission();
122 }
123 catch( Exception ee )
124 {
125 }
126 }
127 }
128 }
129
130 /**
131 * Method invoked by a process to signal that the process has
132 * encounter an error condition.
133 *
134 * @param throwable the error condition
135 * @param fatal if true the process is requesting termination
136 */
137 public void error( Throwable throwable, boolean fatal )
138 {
139 if( fatal )
140 {
141 getLogger().error( "Process raised an fatal error.", throwable );
142 }
143 else
144 {
145 getLogger().warn( "Process raised an non-fatal error.", throwable );
146 }
147 }
148
149 /**
150 * Method invoked by a process to send a arbitary message to the
151 * the callback handler.
152 *
153 * @param message the message
154 */
155 public void info( String message )
156 {
157 getLogger().info( "[" + m_pid + "]: " + message );
158 }
159
160 /**
161 * Method invoked by a process to signal its imminent termination.
162 */
163 public void stopped()
164 {
165 synchronized( m_state )
166 {
167 setProcessState( ProcessState.STOPPED );
168 m_pid = null;
169 }
170 }
171
172 //-------------------------------------------------------------------------------
173 // Application
174 //-------------------------------------------------------------------------------
175
176 /**
177 * Return the process identifier of the process within which the
178 * application is running. If the application is not running a null
179 * value is returned.
180 *
181 * @return the pid
182 */
183 public PID getPID()
184 {
185 return m_pid;
186 }
187
188 /**
189 * Return the application id.
190 *
191 * @return the id
192 */
193 public String getID()
194 {
195 return m_id;
196 }
197
198 /**
199 * Return the profile associated with this application
200 * @return the application profile
201 */
202 public ApplicationDescriptor getApplicationDescriptor()
203 {
204 return m_descriptor;
205 }
206
207 /**
208 * Return the current deployment state of the process.
209 * @return the current process state
210 */
211 public ProcessState getProcessState()
212 {
213 synchronized( m_state )
214 {
215 return m_state;
216 }
217 }
218
219 /**
220 * Start the application.
221 * @exception ApplicationException if an application error occurs
222 */
223 public void start() throws ApplicationException
224 {
225 if( m_descriptor.getStartupPolicy() == StartupPolicy.DISABLED )
226 {
227 final String error =
228 "Cannot start the application ["
229 + m_id
230 + "] due to the DISABLED startup status.";
231 throw new ApplicationException( error );
232 }
233
234 synchronized( m_state )
235 {
236 if( m_state.equals( ProcessState.IDLE ) || m_state.equals( ProcessState.STOPPED ) )
237 {
238 startProcess();
239 }
240 else
241 {
242 final String error =
243 "Cannot start a process in the ["
244 + m_state
245 + "] state.";
246 throw new ApplicationException( error );
247 }
248 }
249 }
250
251 private void startProcess() throws ApplicationException
252 {
253 synchronized( m_state )
254 {
255 setProcessState( ProcessState.STARTING );
256 try
257 {
258 String[] command = getProcessCommand();
259 m_process = Runtime.getRuntime().exec( command, null );
260 }
261 catch( Exception e )
262 {
263 setProcessState( ProcessState.IDLE );
264 final String error =
265 "Process establishment failure.";
266 throw new ApplicationException( error, e );
267 }
268 }
269
270 Logger logger = getLogger();
271 OutputStreamReader output = new OutputStreamReader( logger, m_process.getInputStream() );
272 ErrorStreamReader err = new ErrorStreamReader( logger, m_process.getErrorStream() );
273 output.setDaemon( true );
274 err.setDaemon( true );
275 output.start();
276 err.start();
277
278 /*
279 long timestamp = System.currentTimeMillis();
280 long timeout = timestamp + getStartupTimeout();
281 getLogger().info( "waiting " + getStartupTimeout() );
282
283 while( ( getProcessState() == ProcessState.STARTING )
284 && ( System.currentTimeMillis() < timeout )
285 && ( null == m_error ) )
286 {
287 try
288 {
289 Thread.currentThread().sleep( 600 );
290 }
291 catch( InterruptedException e )
292 {
293 }
294 }
295
296 getLogger().info( "review" );
297 if( getProcessState().equals( ProcessState.STARTING ) )
298 {
299 final String error =
300 "Process failed to start within the timeout period.";
301 getLogger().error( error );
302 handleStop( false );
303 }
304 else if( null != m_error )
305 {
306 final String error =
307 "Application deployment failure.";
308 handleStop( false );
309 throw new ApplicationException( error, m_error );
310 }
311 */
312 }
313
314 /**
315 * Construct the process command parameters sequence.
316 * @exception IOException if an IO error occurs
317 */
318 private String[] getProcessCommand() throws IOException
319 {
320 ArrayList list = new ArrayList();
321
322 String path = m_descriptor.getCodeBaseURISpec();
323 list.add( "metro" );
324
325 //
326 // add system properties
327 //
328
329 Properties properties = m_descriptor.getSystemProperties();
330 properties.setProperty( "dpml.station.key", m_id );
331 properties.setProperty( "dpml.subprocess", "true" );
332 properties.setProperty( "dpml.station.partition", "depot.station." + m_id );
333
334 if( "true".equals( System.getProperty( "dpml.debug" ) ) )
335 {
336 properties.setProperty( "dpml.debug", "true" );
337 }
338
339 if( null == properties.getProperty( "java.util.logging.config.class" ) )
340 {
341 properties.setProperty(
342 "java.util.logging.config.class",
343 "net.dpml.depot.DepotLoggingConfiguration" );
344 }
345
346 if( null == properties.getProperty( "dpml.logging.config" ) )
347 {
348 properties.setProperty(
349 "dpml.logging.config",
350 "local:properties:dpml/station/application" );
351 }
352
353 Enumeration names = properties.propertyNames();
354 while( names.hasMoreElements() )
355 {
356 String name = (String) names.nextElement();
357 String value = properties.getProperty( name );
358 list.add( "-D" + name + "=" + value );
359 }
360
361 //
362 // add the -uri option and codebase parameter
363 //
364
365 list.add( "-uri" );
366 list.add( path );
367
368 //
369 // add options necessary for the handler to establish a callback
370 //
371
372 list.add( "-port" );
373 list.add( "" + m_port );
374 list.add( "-key" );
375 list.add( "" + m_id );
376
377 return (String[]) list.toArray( new String[0] );
378 }
379
380 /**
381 * Stop the application.
382 * @exception RemoteException if a rmote error occurs
383 */
384 public void stop() throws RemoteException
385 {
386 handleStop( true );
387 }
388
389 void shutdown() throws IOException
390 {
391 try
392 {
393 handleStop( false );
394 }
395 catch( Throwable e )
396 {
397 final String error =
398 "Application shutdown error.";
399 getLogger().warn( error, e );
400 }
401 }
402
403 private void handleStop( boolean check ) throws ApplicationException
404 {
405 synchronized( m_state )
406 {
407 if( m_state.equals( ProcessState.IDLE ) )
408 {
409 return;
410 }
411 else if( m_state.equals( ProcessState.STOPPED ) )
412 {
413 return;
414 }
415 else
416 {
417 getLogger().info( "stopping application" );
418 setProcessState( ProcessState.STOPPING );
419 if( m_handler != null )
420 {
421 try
422 {
423 m_handler.decommission();
424 }
425 catch( Throwable e )
426 {
427 final String error =
428 "Component deactivation error reported.";
429 getLogger().warn( error, e );
430 }
431 }
432 setProcessState( ProcessState.STOPPED );
433 if( null != m_process )
434 {
435 m_process.destroy();
436 m_process = null;
437 m_instance = null;
438 }
439 m_pid = null;
440 }
441 }
442 }
443
444 /**
445 * Restart the application.
446 * @exception RemoteException if a rmote error occurs
447 */
448 public void restart() throws RemoteException
449 {
450 stop();
451 start();
452 }
453
454 /**
455 * Return the component instance handler.
456 * @return the instance handler (possibly null)
457 */
458 public Provider getProvider()
459 {
460 return m_instance;
461 }
462
463 /**
464 * Add an application listener.
465 * @param listener the listener to add
466 */
467 public void addApplicationListener( ApplicationListener listener )
468 {
469 super.addListener( listener );
470 }
471
472 /**
473 * Remove an application listener.
474 * @param listener the listener to remove
475 */
476 public void removeApplicationListener( ApplicationListener listener )
477 {
478 super.removeListener( listener );
479 }
480
481 //-------------------------------------------------------------------------------
482 // private utilities
483 //-------------------------------------------------------------------------------
484
485 private void setProcessState( ProcessState state )
486 {
487 synchronized( m_state )
488 {
489 if( m_state != state )
490 {
491 m_state = state;
492 ApplicationEvent event = new ApplicationEvent ( this, state );
493 super.enqueueEvent( event );
494 getLogger().info( "state set to [" + state.getName() + "]" );
495 }
496 }
497 }
498
499 //-------------------------------------------------------------------------------
500 // EventChannel
501 //-------------------------------------------------------------------------------
502
503 /**
504 * Internal event handler.
505 * @param eventObject the event
506 */
507 protected void processEvent( EventObject eventObject )
508 {
509 if( eventObject instanceof ApplicationEvent )
510 {
511 ApplicationEvent event = (ApplicationEvent) eventObject;
512 processApplicationEvent( event );
513 }
514 else
515 {
516 final String error =
517 "Event class not recognized: " + eventObject.getClass().getName();
518 throw new IllegalArgumentException( error );
519 }
520 }
521
522 private void processApplicationEvent( ApplicationEvent event )
523 {
524 EventListener[] listeners = super.listeners();
525 for( int i=0; i < listeners.length; i++ )
526 {
527 EventListener listener = listeners[i];
528 if( listener instanceof ApplicationListener )
529 {
530 try
531 {
532 ApplicationListener applicationListener = (ApplicationListener) listener;
533 applicationListener.stateChanged( event );
534 }
535 catch( ConnectException e )
536 {
537 super.removeListener( listener );
538 }
539 catch( Throwable e )
540 {
541 final String error =
542 "ApplicationListener notification error.";
543 getLogger().error( error, e );
544 }
545 }
546 }
547 }
548
549 private long getStartupTimeout()
550 {
551 return m_descriptor.getStartupTimeout() * 1000000;
552 }
553
554 private long getShutdownTimeout()
555 {
556 return m_descriptor.getShutdownTimeout() * 1000000;
557 }
558 }